package cc.risingsun.wing.core.scanner.repo;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;

import cc.risingsun.wing.core.reflect.ReflectionHelper;
import cc.risingsun.wing.core.scanner.ScannerListener;

/**
 *数据仓库对象扫描监听器,将扫描到的类保存在集合中
 *@author iron.yin@gmail.com
 */
public class RepositoryScannerListener implements ScannerListener{
	
	private final ClassLoader classloader;

    private final Set<Class<?>> classes;

    private final Set<String> annotations;
    
    private final AnnotatedClassVisitor classVisitor;

    /**
     * 创建一个扫描监听器用来检查那些类标注有给定的anntations
     *
     * @param 需要检查的annotation
     */
    public RepositoryScannerListener(Class<? extends Annotation>... annotations) {
        this(ReflectionHelper.getContextClassLoader(), annotations);
    }

    /**
     * 创建一个扫描监听器用来检查,在给定的类加载器中的类,并且标注有制定的anntations
     * 
     * @param 给定的类加载器
     * @param 需要检查的annotation
     */
    public RepositoryScannerListener(ClassLoader classloader,
            Class<? extends Annotation>... annotations) {
        this.classloader = classloader;
        this.classes = new LinkedHashSet<Class<?>>();
        this.annotations = getAnnotationSet(annotations);
        this.classVisitor = new AnnotatedClassVisitor();
    }

    /**
     * 获取标有annotation的类
     * 
     * @return 返回标注的给定的annotation的类的集合
     */
    public Set<Class<?>> getAnnotatedClasses() {
        return classes;
    }
    
    private Set<String> getAnnotationSet(Class<? extends Annotation>... annotations) {
        Set<String> a = new HashSet<String>();
        for (Class<?> c : annotations) {
            a.add("L" + c.getName().replaceAll("\\.", "/") + ";");
        }
        return a;
    }


    public boolean onAccept(String name) {
        return name.endsWith(".class");
    }

    public void onProcess(String name, InputStream in) throws IOException {
        new ClassReader(in).accept(classVisitor, 0);
    }
    
    public void onProcess(String name, byte[] in) throws IOException {
        new ClassReader(in).accept(classVisitor, 0);
    }


    private final class AnnotatedClassVisitor extends ClassVisitor implements Opcodes {

		private String className;
        private boolean isScoped;
        private boolean isAnnotated;
        
        public AnnotatedClassVisitor() {
			super(ASM4);
		}

        public void visit(int version, int access, String name,
                String signature, String superName, String[] interfaces) {
            className = name;
            isScoped = (access & Opcodes.ACC_PUBLIC) != 0;
            isAnnotated = false;
        }

        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            isAnnotated |= annotations.contains(desc);
            return null;
        }

        public void visitInnerClass(String name, String outerName,
                String innerName, int access) {
            if (className.equals(name)) {
                isScoped = (access & Opcodes.ACC_PUBLIC) != 0;
                isScoped &= (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
            }
        }

        public void visitEnd() {
            if (isScoped && isAnnotated) {
                classes.add(getClassForName(className.replaceAll("/", ".")));
            }
        }

        private Class<?> getClassForName(String className) {
            try {
                return ReflectionHelper.classForNameWithException(className, classloader);
            } catch (ClassNotFoundException ex) {
                String s = "A class file of the class name, " +
                        className +
                        "is identified but the class could not be found";
                throw new RuntimeException(s, ex);
            }
        }

    };
	
}
